/*********************************************************************
 * Copyright (C) 2002 Andrew Khan
 * <p>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ***************************************************************************/

package jxl.write.biff;

import jxl.CellType;
import jxl.Hyperlink;
import jxl.Range;
import jxl.biff.*;
import jxl.common.Assert;
import jxl.common.Logger;
import jxl.write.Label;
import jxl.write.WritableCell;
import jxl.write.WritableSheet;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;


/**
 * A hyperlink
 */
public class HyperlinkRecord extends WritableRecordData {
    /**
     * The logger
     */
    private static Logger logger = Logger.getLogger(HyperlinkRecord.class);

    /**
     * The first row
     */
    private int firstRow;
    /**
     * The last row
     */
    private int lastRow;
    /**
     * The first column
     */
    private int firstColumn;
    /**
     * The last column
     */
    private int lastColumn;

    /**
     * The URL referred to by this hyperlink
     */
    private URL url;

    /**
     * The local file referred to by this hyperlink
     */
    private File file;

    /**
     * The location in this workbook referred to by this hyperlink
     */
    private String location;

    /**
     * The cell contents of the cell which activate this hyperlink
     */
    private String contents;

    /**
     * The type of this hyperlink
     */
    private LinkType linkType;

    /**
     * The data for this hyperlink
     */
    private byte[] data;

    /**
     * The range of this hyperlink.  When creating a hyperlink, this will
     * be null until the hyperlink is added to the sheet
     */
    private Range range;

    /**
     * The sheet containing this hyperlink
     */
    private WritableSheet sheet;

    /**
     * Indicates whether this record has been modified since it was copied
     */
    private boolean modified;

    /**
     * The excel type of hyperlink
     */
    private static class LinkType {
    }

    ;

    private static final LinkType urlLink = new LinkType();
    private static final LinkType fileLink = new LinkType();
    private static final LinkType uncLink = new LinkType();
    private static final LinkType workbookLink = new LinkType();
    private static final LinkType unknown = new LinkType();

    /**
     * Constructs this object from the readable spreadsheet
     *
     * @param hl the hyperlink from the read spreadsheet
     */
    protected HyperlinkRecord(Hyperlink h, WritableSheet s) {
        super(Type.HLINK);

        if (h instanceof jxl.read.biff.HyperlinkRecord) {
            copyReadHyperlink(h, s);
        } else {
            copyWritableHyperlink(h, s);
        }
    }

    /**
     * Copies a hyperlink read in from a read only sheet
     */
    private void copyReadHyperlink(Hyperlink h, WritableSheet s) {
        jxl.read.biff.HyperlinkRecord hl = (jxl.read.biff.HyperlinkRecord) h;

        data = hl.getRecord().getData();
        sheet = s;

        // Populate this hyperlink with the copied data
        firstRow = hl.getRow();
        firstColumn = hl.getColumn();
        lastRow = hl.getLastRow();
        lastColumn = hl.getLastColumn();
        range = new SheetRangeImpl(s,
                firstColumn, firstRow,
                lastColumn, lastRow);

        linkType = unknown;

        if (hl.isFile()) {
            linkType = fileLink;
            file = hl.getFile();
        } else if (hl.isURL()) {
            linkType = urlLink;
            url = hl.getURL();
        } else if (hl.isLocation()) {
            linkType = workbookLink;
            location = hl.getLocation();
        }

        modified = false;
    }

    /**
     * Copies a hyperlink read in from a writable sheet.
     * Used when copying writable sheets
     *
     * @param hl the hyperlink from the read spreadsheet
     */
    private void copyWritableHyperlink(Hyperlink hl, WritableSheet s) {
        HyperlinkRecord h = (HyperlinkRecord) hl;

        firstRow = h.firstRow;
        lastRow = h.lastRow;
        firstColumn = h.firstColumn;
        lastColumn = h.lastColumn;

        if (h.url != null) {
            try {
                url = new URL(h.url.toString());
            } catch (MalformedURLException e) {
                // should never get a malformed url as a result url.toString()
                Assert.verify(false);
            }
        }

        if (h.file != null) {
            file = new File(h.file.getPath());
        }

        location = h.location;
        contents = h.contents;
        linkType = h.linkType;
        modified = true;

        sheet = s;
        range = new SheetRangeImpl(s,
                firstColumn, firstRow,
                lastColumn, lastRow);
    }

    /**
     * Constructs a URL hyperlink to a range of cells
     *
     * @param col the column containing this hyperlink
     * @param row the row containing this hyperlink
     * @param lastcol the last column which activates this hyperlink
     * @param lastrow the last row which activates this hyperlink
     * @param url the hyperlink
     * @param desc the description
     */
    protected HyperlinkRecord(int col, int row,
                              int lastcol, int lastrow,
                              URL url,
                              String desc) {
        super(Type.HLINK);

        firstColumn = col;
        firstRow = row;

        lastColumn = Math.max(firstColumn, lastcol);
        lastRow = Math.max(firstRow, lastrow);

        this.url = url;
        contents = desc;

        linkType = urlLink;

        modified = true;
    }

    /**
     * Constructs a File hyperlink to a range of cells
     *
     * @param col the column containing this hyperlink
     * @param row the row containing this hyperlink
     * @param lastcol the last column which activates this hyperlink
     * @param lastrow the last row which activates this hyperlink
     * @param file the hyperlink
     * @param desc the description
     */
    protected HyperlinkRecord(int col, int row, int lastcol, int lastrow,
                              File file, String desc) {
        super(Type.HLINK);

        firstColumn = col;
        firstRow = row;

        lastColumn = Math.max(firstColumn, lastcol);
        lastRow = Math.max(firstRow, lastrow);
        contents = desc;

        this.file = file;

        if (file.getPath().startsWith("\\\\")) {
            linkType = uncLink;
        } else {
            linkType = fileLink;
        }

        modified = true;
    }

    /**
     * Constructs a hyperlink to some cells within this workbook
     *
     * @param col the column containing this hyperlink
     * @param row the row containing this hyperlink
     * @param lastcol the last column which activates this hyperlink
     * @param lastrow the last row which activates this hyperlink
     * @param desc the contents of the cell which describe this hyperlink
     * @param sheet the sheet containing the cells to be linked to
     * @param destcol the column number of the first destination linked cell
     * @param destrow the row number of the first destination linked cell
     * @param lastdestcol the column number of the last destination linked cell
     * @param lastdestrow the row number of the last destination linked cell
     */
    protected HyperlinkRecord(int col, int row,
                              int lastcol, int lastrow,
                              String desc,
                              WritableSheet s,
                              int destcol, int destrow,
                              int lastdestcol, int lastdestrow) {
        super(Type.HLINK);

        firstColumn = col;
        firstRow = row;

        lastColumn = Math.max(firstColumn, lastcol);
        lastRow = Math.max(firstRow, lastrow);

        setLocation(s, destcol, destrow, lastdestcol, lastdestrow);
        contents = desc;

        linkType = workbookLink;

        modified = true;
    }

    /**
     * Determines whether this is a hyperlink to a file
     *
     * @return TRUE if this is a hyperlink to a file, FALSE otherwise
     */
    public boolean isFile() {
        return linkType == fileLink;
    }

    /**
     * Determines whether this is a hyperlink to a UNC
     *
     * @return TRUE if this is a hyperlink to a UNC, FALSE otherwise
     */
    public boolean isUNC() {
        return linkType == uncLink;
    }

    /**
     * Determines whether this is a hyperlink to a web resource
     *
     * @return TRUE if this is a URL
     */
    public boolean isURL() {
        return linkType == urlLink;
    }

    /**
     * Determines whether this is a hyperlink to a location in this workbook
     *
     * @return TRUE if this is a link to an internal location
     */
    public boolean isLocation() {
        return linkType == workbookLink;
    }

    /**
     * Returns the row number of the top left cell
     *
     * @return the row number of this cell
     */
    public int getRow() {
        return firstRow;
    }

    /**
     * Returns the column number of the top left cell
     *
     * @return the column number of this cell
     */
    public int getColumn() {
        return firstColumn;
    }

    /**
     * Returns the row number of the bottom right cell
     *
     * @return the row number of this cell
     */
    public int getLastRow() {
        return lastRow;
    }

    /**
     * Returns the column number of the bottom right cell
     *
     * @return the column number of this cell
     */
    public int getLastColumn() {
        return lastColumn;
    }

    /**
     * Gets the URL referenced by this Hyperlink
     *
     * @return the URL, or NULL if this hyperlink is not a URL
     */
    public URL getURL() {
        return url;
    }

    /**
     * Returns the local file eferenced by this Hyperlink
     *
     * @return the file, or NULL if this hyperlink is not a file
     */
    public File getFile() {
        return file;
    }

    /**
     * Gets the binary data to be written to the output file
     *
     * @return the data to write to file
     */
    public byte[] getData() {
        if (!modified) {
            return data;
        }

        // Build up the jxl.common.data
        byte[] commonData = new byte[32];

        // Set the range of cells this hyperlink applies to
        IntegerHelper.getTwoBytes(firstRow, commonData, 0);
        IntegerHelper.getTwoBytes(lastRow, commonData, 2);
        IntegerHelper.getTwoBytes(firstColumn, commonData, 4);
        IntegerHelper.getTwoBytes(lastColumn, commonData, 6);

        // Some inexplicable byte sequence
        commonData[8] = (byte) 0xd0;
        commonData[9] = (byte) 0xc9;
        commonData[10] = (byte) 0xea;
        commonData[11] = (byte) 0x79;
        commonData[12] = (byte) 0xf9;
        commonData[13] = (byte) 0xba;
        commonData[14] = (byte) 0xce;
        commonData[15] = (byte) 0x11;
        commonData[16] = (byte) 0x8c;
        commonData[17] = (byte) 0x82;
        commonData[18] = (byte) 0x0;
        commonData[19] = (byte) 0xaa;
        commonData[20] = (byte) 0x0;
        commonData[21] = (byte) 0x4b;
        commonData[22] = (byte) 0xa9;
        commonData[23] = (byte) 0x0b;
        commonData[24] = (byte) 0x2;
        commonData[25] = (byte) 0x0;
        commonData[26] = (byte) 0x0;
        commonData[27] = (byte) 0x0;

        // Set up the option flags to indicate the type of this URL.  There
        // is no description
        int optionFlags = 0;
        if (isURL()) {
            optionFlags = 3;

            if (contents != null) {
                optionFlags |= 0x14;
            }
        } else if (isFile()) {
            optionFlags = 1;

            if (contents != null) {
                optionFlags |= 0x14;
            }
        } else if (isLocation()) {
            optionFlags = 8;
        } else if (isUNC()) {
            optionFlags = 259;
        }

        IntegerHelper.getFourBytes(optionFlags, commonData, 28);

        if (isURL()) {
            data = getURLData(commonData);
        } else if (isFile()) {
            data = getFileData(commonData);
        } else if (isLocation()) {
            data = getLocationData(commonData);
        } else if (isUNC()) {
            data = getUNCData(commonData);
        }

        return data;
    }

    /**
     * A standard toString method
     *
     * @return the contents of this object as a string
     */
    public String toString() {
        if (isFile()) {
            return file.toString();
        } else if (isURL()) {
            return url.toString();
        } else if (isUNC()) {
            return file.toString();
        } else {
            return "";
        }
    }

    /**
     * Gets the range of cells which activate this hyperlink
     * The get sheet index methods will all return -1, because the
     * cells will all be present on the same sheet
     *
     * @return the range of cells which activate the hyperlink or NULL
     * if this hyperlink has not been added to the sheet
     */
    public Range getRange() {
        return range;
    }

    /**
     * Sets the URL of this hyperlink
     *
     * @param url the url
     */
    public void setURL(URL url) {
        URL prevurl = this.url;
        linkType = urlLink;
        file = null;
        location = null;
        contents = null;
        this.url = url;
        modified = true;

        if (sheet == null) {
            // hyperlink has not been added to the sheet yet, so simply return
            return;
        }

        // Change the label on the sheet if it was a string representation of the
        // URL
        WritableCell wc = sheet.getWritableCell(firstColumn, firstRow);

        if (wc.getType() == CellType.LABEL) {
            Label l = (Label) wc;
            String prevurlString = prevurl.toString();
            String prevurlString2 = "";
            if (prevurlString.charAt(prevurlString.length() - 1) == '/' ||
                    prevurlString.charAt(prevurlString.length() - 1) == '\\') {
                prevurlString2 = prevurlString.substring(0,
                        prevurlString.length() - 1);
            }

            if (l.getString().equals(prevurlString) ||
                    l.getString().equals(prevurlString2)) {
                l.setString(url.toString());
            }
        }
    }

    /**
     * Sets the file activated by this hyperlink
     *
     * @param file the file
     */
    public void setFile(File file) {
        linkType = fileLink;
        url = null;
        location = null;
        contents = null;
        this.file = file;
        modified = true;

        if (sheet == null) {
            // hyperlink has not been added to the sheet yet, so simply return
            return;
        }

        // Change the label on the sheet
        WritableCell wc = sheet.getWritableCell(firstColumn, firstRow);

        Assert.verify(wc.getType() == CellType.LABEL);

        Label l = (Label) wc;
        l.setString(file.toString());
    }

    /**
     * Sets the location of the cells to be linked to within this workbook
     *
     * @param desc the label describing the link
     * @param sheet the sheet containing the cells to be linked to
     * @param destcol the column number of the first destination linked cell
     * @param destrow the row number of the first destination linked cell
     * @param lastdestcol the column number of the last destination linked cell
     * @param lastdestrow the row number of the last destination linked cell
     */
    protected void setLocation(String desc,
                               WritableSheet sheet,
                               int destcol, int destrow,
                               int lastdestcol, int lastdestrow) {
        linkType = workbookLink;
        url = null;
        file = null;
        modified = true;
        contents = desc;

        setLocation(sheet, destcol, destrow, lastdestcol, lastdestrow);

        if (sheet == null) {
            // hyperlink has not been added to the sheet yet, so simply return
            return;
        }

        // Change the label on the sheet
        WritableCell wc = sheet.getWritableCell(firstColumn, firstRow);

        Assert.verify(wc.getType() == CellType.LABEL);

        Label l = (Label) wc;
        l.setString(desc);
    }

    /**
     * Initializes the location from the data passed in
     *
     * @param sheet the sheet containing the cells to be linked to
     * @param destcol the column number of the first destination linked cell
     * @param destrow the row number of the first destination linked cell
     * @param lastdestcol the column number of the last destination linked cell
     * @param lastdestrow the row number of the last destination linked cell
     */
    private void setLocation(WritableSheet sheet,
                             int destcol, int destrow,
                             int lastdestcol, int lastdestrow) {
        StringBuffer sb = new StringBuffer();
        sb.append('\'');

        if (sheet.getName().indexOf('\'') == -1) {
            sb.append(sheet.getName());
        } else {
            // sb.append(sheet.getName().replaceAll("'", "''"));

            // Can't use replaceAll as it is only 1.4 compatible, so have to
            // do this the tedious way
            String sheetName = sheet.getName();
            int pos = 0;
            int nextPos = sheetName.indexOf('\'', pos);

            while (nextPos != -1 && pos < sheetName.length()) {
                sb.append(sheetName.substring(pos, nextPos));
                sb.append("''");
                pos = nextPos + 1;
                nextPos = sheetName.indexOf('\'', pos);
            }
            sb.append(sheetName.substring(pos));
        }

        sb.append('\'');
        sb.append('!');

        lastdestcol = Math.max(destcol, lastdestcol);
        lastdestrow = Math.max(destrow, lastdestrow);

        CellReferenceHelper.getCellReference(destcol, destrow, sb);
        sb.append(':');
        CellReferenceHelper.getCellReference(lastdestcol, lastdestrow, sb);

        location = sb.toString();
    }

    /**
     * A row has been inserted, so adjust the range objects accordingly
     *
     * @param r the row which has been inserted
     */
    void insertRow(int r) {
        // This will not be called unless the hyperlink has been added to the
        // sheet
        Assert.verify(sheet != null && range != null);

        if (r > lastRow) {
            return;
        }

        if (r <= firstRow) {
            firstRow++;
            modified = true;
        }

        if (r <= lastRow) {
            lastRow++;
            modified = true;
        }

        if (modified) {
            range = new SheetRangeImpl(sheet,
                    firstColumn, firstRow,
                    lastColumn, lastRow);
        }
    }

    /**
     * A column has been inserted, so adjust the range objects accordingly
     *
     * @param c the column which has been inserted
     */
    void insertColumn(int c) {
        // This will not be called unless the hyperlink has been added to the
        // sheet
        Assert.verify(sheet != null && range != null);

        if (c > lastColumn) {
            return;
        }

        if (c <= firstColumn) {
            firstColumn++;
            modified = true;
        }

        if (c <= lastColumn) {
            lastColumn++;
            modified = true;
        }

        if (modified) {
            range = new SheetRangeImpl(sheet,
                    firstColumn, firstRow,
                    lastColumn, lastRow);
        }
    }

    /**
     * A row has been removed, so adjust the range objects accordingly
     *
     * @param r the row which has been inserted
     */
    void removeRow(int r) {
        // This will not be called unless the hyperlink has been added to the
        // sheet
        Assert.verify(sheet != null && range != null);

        if (r > lastRow) {
            return;
        }

        if (r < firstRow) {
            firstRow--;
            modified = true;
        }

        if (r < lastRow) {
            lastRow--;
            modified = true;
        }

        if (modified) {
            Assert.verify(range != null);
            range = new SheetRangeImpl(sheet,
                    firstColumn, firstRow,
                    lastColumn, lastRow);
        }
    }

    /**
     * A column has been removed, so adjust the range objects accordingly
     *
     * @param c the column which has been removed
     */
    void removeColumn(int c) {
        // This will not be called unless the hyperlink has been added to the
        // sheet
        Assert.verify(sheet != null && range != null);

        if (c > lastColumn) {
            return;
        }

        if (c < firstColumn) {
            firstColumn--;
            modified = true;
        }

        if (c < lastColumn) {
            lastColumn--;
            modified = true;
        }

        if (modified) {
            Assert.verify(range != null);
            range = new SheetRangeImpl(sheet,
                    firstColumn, firstRow,
                    lastColumn, lastRow);
        }
    }

    /**
     * Gets the hyperlink stream specific to a URL link
     *
     * @param cd the data jxl.common.for all types of hyperlink
     * @return the raw data for a URL hyperlink
     */
    private byte[] getURLData(byte[] cd) {
        String urlString = url.toString();

        int dataLength = cd.length + 20 + (urlString.length() + 1) * 2;

        if (contents != null) {
            dataLength += 4 + (contents.length() + 1) * 2;
        }

        byte[] d = new byte[dataLength];

        System.arraycopy(cd, 0, d, 0, cd.length);

        int urlPos = cd.length;

        if (contents != null) {
            IntegerHelper.getFourBytes(contents.length() + 1, d, urlPos);
            StringHelper.getUnicodeBytes(contents, d, urlPos + 4);
            urlPos += (contents.length() + 1) * 2 + 4;
        }

        // Inexplicable byte sequence
        d[urlPos] = (byte) 0xe0;
        d[urlPos + 1] = (byte) 0xc9;
        d[urlPos + 2] = (byte) 0xea;
        d[urlPos + 3] = (byte) 0x79;
        d[urlPos + 4] = (byte) 0xf9;
        d[urlPos + 5] = (byte) 0xba;
        d[urlPos + 6] = (byte) 0xce;
        d[urlPos + 7] = (byte) 0x11;
        d[urlPos + 8] = (byte) 0x8c;
        d[urlPos + 9] = (byte) 0x82;
        d[urlPos + 10] = (byte) 0x0;
        d[urlPos + 11] = (byte) 0xaa;
        d[urlPos + 12] = (byte) 0x0;
        d[urlPos + 13] = (byte) 0x4b;
        d[urlPos + 14] = (byte) 0xa9;
        d[urlPos + 15] = (byte) 0x0b;

        // Number of characters in the url, including a zero trailing character
        IntegerHelper.getFourBytes((urlString.length() + 1) * 2, d, urlPos + 16);

        // Put the url into the data string
        StringHelper.getUnicodeBytes(urlString, d, urlPos + 20);

        return d;
    }

    /**
     * Gets the hyperlink stream specific to a URL link
     *
     * @param cd the data jxl.common.for all types of hyperlink
     * @return the raw data for a URL hyperlink
     */
    private byte[] getUNCData(byte[] cd) {
        String uncString = file.getPath();

        byte[] d = new byte[cd.length + uncString.length() * 2 + 2 + 4];
        System.arraycopy(cd, 0, d, 0, cd.length);

        int urlPos = cd.length;

        // The length of the unc string, including zero terminator
        int length = uncString.length() + 1;
        IntegerHelper.getFourBytes(length, d, urlPos);

        // Place the string into the stream
        StringHelper.getUnicodeBytes(uncString, d, urlPos + 4);

        return d;
    }

    /**
     * Gets the hyperlink stream specific to a local file link
     *
     * @param cd the data jxl.common.for all types of hyperlink
     * @return the raw data for a URL hyperlink
     */
    private byte[] getFileData(byte[] cd) {
        // Build up the directory hierarchy in reverse order
        ArrayList path = new ArrayList();
        ArrayList shortFileName = new ArrayList();
        path.add(file.getName());
        shortFileName.add(getShortName(file.getName()));

        File parent = file.getParentFile();
        while (parent != null) {
            path.add(parent.getName());
            shortFileName.add(getShortName(parent.getName()));
            parent = parent.getParentFile();
        }

        // Deduce the up directory level count and remove the directory from
        // the path
        int upLevelCount = 0;
        int pos = path.size() - 1;
        boolean upDir = true;

        while (upDir) {
            String s = (String) path.get(pos);
            if (s.equals("..")) {
                upLevelCount++;
                path.remove(pos);
                shortFileName.remove(pos);
            } else {
                upDir = false;
            }

            pos--;
        }

        StringBuffer filePathSB = new StringBuffer();
        StringBuffer shortFilePathSB = new StringBuffer();

        if (file.getPath().charAt(1) == ':') {
            char driveLetter = file.getPath().charAt(0);
            if (driveLetter != 'C' && driveLetter != 'c') {
                filePathSB.append(driveLetter);
                filePathSB.append(':');
                shortFilePathSB.append(driveLetter);
                shortFilePathSB.append(':');
            }
        }

        for (int i = path.size() - 1; i >= 0; i--) {
            filePathSB.append((String) path.get(i));
            shortFilePathSB.append((String) shortFileName.get(i));

            if (i != 0) {
                filePathSB.append("\\");
                shortFilePathSB.append("\\");
            }
        }


        String filePath = filePathSB.toString();
        String shortFilePath = shortFilePathSB.toString();

        int dataLength = cd.length +
                4 + (shortFilePath.length() + 1) + // short file name
                16 + // inexplicable byte sequence
                2 + // up directory level count
                8 + (filePath.length() + 1) * 2 + // long file name
                24; // inexplicable byte sequence


        if (contents != null) {
            dataLength += 4 + (contents.length() + 1) * 2;
        }

        // Copy across the jxl.common.data into the new array
        byte[] d = new byte[dataLength];

        System.arraycopy(cd, 0, d, 0, cd.length);

        int filePos = cd.length;

        // Add in the description text
        if (contents != null) {
            IntegerHelper.getFourBytes(contents.length() + 1, d, filePos);
            StringHelper.getUnicodeBytes(contents, d, filePos + 4);
            filePos += (contents.length() + 1) * 2 + 4;
        }

        int curPos = filePos;

        // Inexplicable byte sequence
        d[curPos] = (byte) 0x03;
        d[curPos + 1] = (byte) 0x03;
        d[curPos + 2] = (byte) 0x0;
        d[curPos + 3] = (byte) 0x0;
        d[curPos + 4] = (byte) 0x0;
        d[curPos + 5] = (byte) 0x0;
        d[curPos + 6] = (byte) 0x0;
        d[curPos + 7] = (byte) 0x0;
        d[curPos + 8] = (byte) 0xc0;
        d[curPos + 9] = (byte) 0x0;
        d[curPos + 10] = (byte) 0x0;
        d[curPos + 11] = (byte) 0x0;
        d[curPos + 12] = (byte) 0x0;
        d[curPos + 13] = (byte) 0x0;
        d[curPos + 14] = (byte) 0x0;
        d[curPos + 15] = (byte) 0x46;

        curPos += 16;

        // The directory up level count
        IntegerHelper.getTwoBytes(upLevelCount, d, curPos);
        curPos += 2;

        // The number of bytes in the short file name, including zero terminator
        IntegerHelper.getFourBytes((shortFilePath.length() + 1), d, curPos);

        // The short file name
        StringHelper.getBytes(shortFilePath, d, curPos + 4);

        curPos += 4 + (shortFilePath.length() + 1);

        // Inexplicable byte sequence
        d[curPos] = (byte) 0xff;
        d[curPos + 1] = (byte) 0xff;
        d[curPos + 2] = (byte) 0xad;
        d[curPos + 3] = (byte) 0xde;
        d[curPos + 4] = (byte) 0x0;
        d[curPos + 5] = (byte) 0x0;
        d[curPos + 6] = (byte) 0x0;
        d[curPos + 7] = (byte) 0x0;
        d[curPos + 8] = (byte) 0x0;
        d[curPos + 9] = (byte) 0x0;
        d[curPos + 10] = (byte) 0x0;
        d[curPos + 11] = (byte) 0x0;
        d[curPos + 12] = (byte) 0x0;
        d[curPos + 13] = (byte) 0x0;
        d[curPos + 14] = (byte) 0x0;
        d[curPos + 15] = (byte) 0x0;
        d[curPos + 16] = (byte) 0x0;
        d[curPos + 17] = (byte) 0x0;
        d[curPos + 18] = (byte) 0x0;
        d[curPos + 19] = (byte) 0x0;
        d[curPos + 20] = (byte) 0x0;
        d[curPos + 21] = (byte) 0x0;
        d[curPos + 22] = (byte) 0x0;
        d[curPos + 23] = (byte) 0x0;

        curPos += 24;

        // Size of the long file name data in bytes, including inexplicable data
        // fields
        int size = 6 + filePath.length() * 2;
        IntegerHelper.getFourBytes(size, d, curPos);
        curPos += 4;

        // The number of bytes in the long file name
        // NOT including zero terminator
        IntegerHelper.getFourBytes((filePath.length()) * 2, d, curPos);
        curPos += 4;

        // Inexplicable bytes
        d[curPos] = (byte) 0x3;
        d[curPos + 1] = (byte) 0x0;

        curPos += 2;

        // The long file name
        StringHelper.getUnicodeBytes(filePath, d, curPos);
        curPos += (filePath.length() + 1) * 2;
    

    /*
    curPos += 24;
    int nameLength = filePath.length() * 2;

    // Size of the file link 
    IntegerHelper.getFourBytes(nameLength+6, d, curPos);

    // Number of characters
    IntegerHelper.getFourBytes(nameLength, d, curPos+4);

    // Inexplicable byte sequence
    d[curPos+8] = 0x03;
    
    // The long file name
    StringHelper.getUnicodeBytes(filePath, d, curPos+10);
    */

        return d;
    }

    /**
     * Gets the DOS short file name in 8.3 format of the name passed in
     *
     * @param s the name
     * @return the dos short name
     */
    private String getShortName(String s) {
        int sep = s.indexOf('.');

        String prefix = null;
        String suffix = null;

        if (sep == -1) {
            prefix = s;
            suffix = "";
        } else {
            prefix = s.substring(0, sep);
            suffix = s.substring(sep + 1);
        }

        if (prefix.length() > 8) {
            prefix = prefix.substring(0, 6) + "~" + (prefix.length() - 8);
            prefix = prefix.substring(0, 8);
        }

        suffix = suffix.substring(0, Math.min(3, suffix.length()));

        if (suffix.length() > 0) {
            return prefix + '.' + suffix;
        } else {
            return prefix;
        }
    }

    /**
     * Gets the hyperlink stream specific to a location link
     *
     * @param cd the data jxl.common.for all types of hyperlink
     * @return the raw data for a URL hyperlink
     */
    private byte[] getLocationData(byte[] cd) {
        byte[] d = new byte[cd.length + 4 + (location.length() + 1) * 2];
        System.arraycopy(cd, 0, d, 0, cd.length);

        int locPos = cd.length;

        // The number of chars in the location string, plus a 0 terminator
        IntegerHelper.getFourBytes(location.length() + 1, d, locPos);

        // Get the location
        StringHelper.getUnicodeBytes(location, d, locPos + 4);

        return d;
    }


    /**
     * Initializes the range when this hyperlink is added to the sheet
     *
     * @param s the sheet containing this hyperlink
     */
    void initialize(WritableSheet s) {
        sheet = s;
        range = new SheetRangeImpl(s,
                firstColumn, firstRow,
                lastColumn, lastRow);
    }

    /**
     * Called by the worksheet.  Gets the string contents to put into the cell
     * containing this hyperlink
     *
     * @return the string contents for the hyperlink cell
     */
    String getContents() {
        return contents;
    }

    /**
     * Sets the description
     *
     * @param desc the description
     */
    protected void setContents(String desc) {
        contents = desc;
        modified = true;
    }
}







